Android底层:通熟易懂的分析binder--2. binder进程通信协议及“记录链路”结构体
前言
Android底层:通熟易懂分析binder:1.binder准备工作主要介绍了binder进程通信之前需要做的准备工作有哪些,既然binder准备工作做好了,那下一步就是通信了,在讲解通信之前,我希望先把通信的基础知识讲清楚,比如进程与driver层之间的通信协议是啥?binder_node,binder_ref,binder_thread,binder_proc这些东西到底都是啥?在通信过程中起啥作用?Binder,BinderProxy,BpBinder,BBinder又是啥?只有把这些基础知识了解清楚,对于后面的内容理解才能胸有成竹。
本篇内容
1. 从“表面”看什么是binder服务?,什么是binder服务引用?
2. 协议
2.1 进程与driver层的通信协议
2.2 "driver层代理进程"之间的通信协议
2.3 binder通信协议总结
3. "记录链路"结构体
3.1 binder_node
3.2 binder_ref
3.3 binder_thread
3.4 binder_proc
3.5 总结
4. 总结
会用到的词语:
client进程:binder进程通信的client端(使用binder服务)
server进程:binder进程通信的server端(提供binder服务)
driver层代理进程:driver层中其实是有一个结构体(binder_proc)与上层进程有一个对应关系的,这个东东我就起了个名字叫 driver层代理进程。
01
—
从“表面”看binder服务/引用是啥?
从“表面”看是指我们从app开发的角度来看,这个角度是我们能看的着,并且经常用的着。来看一段代码
public interface IXXX extends android.os.IInterface {
/**
* Local-side IPC implementation stub class.
*/
public static abstract class Stub extends android.os.Binder implements IXXX {
private static final java.lang.String DESCRIPTOR
= "...IXXX";
/**
* Construct the stub at attach it to the interface.
*/
public Stub() {
this.attachInterface(this, DESCRIPTOR);
}
public static IXXX asInterface(android.os.IBinder obj) {
if ((obj == null)) {
return null;
}
android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
if (((iin != null) && (iin instanceof IXXX))) {
return ((IXXX) iin);
}
return new IXXX.Stub.Proxy(obj);
}
@Override
public android.os.IBinder asBinder() {
return this;
}
@Override
public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply,
int flags) throws android.os.RemoteException {
java.lang.String descriptor = DESCRIPTOR;
switch (code) {
case INTERFACE_TRANSACTION: {
reply.writeString(descriptor);
return true;
}
case TRANSACTION_methodXX: {
data.enforceInterface(descriptor);
this.methodXX(arg);
reply.writeNoException();
return true;
}
default: {
return super.onTransact(code, data, reply, flags);
}
}
}
// 对方进程使用代理类
private static class Proxy implements IXXX {
private android.os.IBinder mRemote;
Proxy(android.os.IBinder remote) {
mRemote = remote;
}
@Override
public android.os.IBinder asBinder() {
return mRemote;
}
public java.lang.String getInterfaceDescriptor() {
return DESCRIPTOR;
}
@Override
public void methodXX(param)
throws android.os.RemoteException {
android.os.Parcel _data = android.os.Parcel.obtain();
android.os.Parcel _reply = android.os.Parcel.obtain();
try {
_data.writeInterfaceToken(DESCRIPTOR);
_data.writeStrongBinder((((cb != null)) ? (cb.asBinder()) : (null)));
mRemote.transact(Stub.TRANSACTION_methodXX, _data, _reply, 0);
_reply.readException();
} finally {
_reply.recycle();
_data.recycle();
}
}
}
static final int TRANSACTION_methodXX = (
android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
}
public void methodXX(param) throws android.os.RemoteException;
}
上面的代码是android 的aidl工具解析aidl文件生成的一个java文件,整个文件很清晰,定义了
Stub
Stub这个类大家肯定不陌生,在Service组件中的onBind方法中会返回这个类的子类的对象(这个对象就是服务的提供者),Stub是定义在server进程中的,因此在java层可以理解为只要是继承了Binder类的类它就可以作为binder服务。
Proxy
Proxy这个类,从类名上就能看出它是在client进程中用到的,里面有一个很重要的属性 android.os.IBinder mRemote , 这个属性从名字上更能看出它是指代server进程的Stub的子类对象的,mRemote它具体的类型是BinderProxy。
binder服务
java层中只要继承了Binder类就可以作为binder服务了,native层Binder对应的类是BBinder。咱们通常的用法是把Binder通过Service组件的onBind方法返回,Binder类的对象还可以在Activity或者其他代码中通过Parcel传递到driver层,作为binder进程通信的binder服务
binder服务引用
binder服务引用在java层是BinderProxy,在native层是BpBinder。BinderProxy的对象是由jni层生成的,它会指向native层的BpBinder,BpBinder有一个很重要的属性mHandle,后面会着中说它的作用,以及生成规则。
Binder与BinderProxy是对应关系, BBinder与BpBinder是对应关系,binder进程通信从表面上看实际是Binder与BinderProxy 或 BBinder与BpBinder之间的通信。
Binder,BinderProxy,BBinder,BpBinder都可以被写入Parcel中,传递到driver层。
02
—
协议
不管是socket通信还是web通信,它们都需要定义自己的协议,比如socket通信中,client与server端需要定义好协议,如code值都有啥,数据以啥开头以啥结尾,数据长度是多少。web通信的协议就很多了比如http,tcp,udp等等。那同理binder进程之间通信也需要定义自己的协议,因为binder进程通信分为:
那我们从这两方面来介绍下。
driver层代理进程:driver层中其实是有一个结构体(binder_proc)与上层进程有一个对应关系的,这个东东我就起了个名字叫 driver层代理进程
2.1 进程与driver层通信的协议
Android底层:通熟易懂分析binder:1.binder准备工作提到进程与driver层通信中最为频繁的结构体是**binder_write_read**,binder_write_read中的**write_buffer**是进程发送给driver层的数据,**read_buffer**是driver层发送给进程的数据。我们通过socket通信时,客户端和服务端传输的数据是需要定义一些协议,比如下面的协议
cmd + 数据长度 + 数据
这样根据cmd就需要调用哪些功能了,其次在把数据拿出来进行处理。
同理,通过binder_write_read进行进程与driver层通信时,也需要定义一些协议,协议如下:
cmd + 数据(binder_transaction_data)
1. 进程发送给driver层的数据时,cmd是格式是 BC_XX ,以BC开头,
2. driver层返回给进程的数据格式是 BR_XX, 以BR开头
cmd
cmd有BR_TRANSACTION,BR_TRANSACTION_COMPLETE,BR_REPLY,BR_OK
BC_TRANSACTION,BC_REPLY,BC_FREE_BUFFER等,
其中BR_TRANSACTION,BR_REPLY,BC_TRANSACTION,BC_REPLY使用最多主要用来进程之间通信的。
数据
数据是存放在binder_transaction_data,这里的数据其实主要是上层传递的binder服务(BBinder)或binder服务引用(BpBinder),方法code值,方法参数,看下它的定义
struct binder_transaction_data
{
union {
__u32 handle;
binder_uintptr_t ptr;
} target;
binder_uintptr_t cookie;
// code 调用的服务的方法对应的code值
__u32 code;
__u32 flags;
// 发送者的进程id
pid_t sender_pid;
uid_t sender_euid;
// 服务方法接收的参数信息长度
binder_size_t data_size;
binder_size_t offsets_size;
union {
struct {
// 服务方法接收的参数
binder_uintptr_t buffer;
binder_uintptr_t offsets;
} ptr;
__u8 buf[8];
} data;
};
target 存放了 binder服务或者binder服务引用,里面的handle,ptr 分别对应BpBinder,BBinder,target的主要作用就是在告诉driver层我要调用哪个服务
code 对应的是方法的code值
sender_pid 发送者的进程id
data_size,offsets_size 参数长度信息
data 就是具体的参数信息了。
2.2 "driver层代理进程"之间的通信协议
"driver层代理进程"之间的通信是binder进程通信的基础,每个"driver层代理进程"可以理解为代表者上层的一个进程。它们之间通信协议也是
cmd + 数据
协议是被放在binder_work结构体中的,
cmd
cmd有BINDER_WORK_TRANSACTION,BINDER_WORK_TRANSACTION_COMPLETE,BINDER_WORK_NODE,BINDER_WORK_DEAD_BINDER等,它们都是以BINDER_WORK开头的。
数据
数据是存放在binder_transaction结构体中的,先看下它的结构,后面会着中介绍它
struct binder_transaction {
int debug_id;
struct binder_work work;
// from信息是用来进行回复给对方信息用的,使用在同步通信环节
struct binder_thread *from;
struct binder_transaction *from_parent;
struct binder_proc *to_proc;
struct binder_thread *to_thread;
struct binder_transaction *to_parent;
unsigned need_reply:1;
/* unsigned is_dead:1; */ /* not used at the moment */
// 数据信息,主要是传递的c参数
struct binder_buffer *buffer;
// 调用的方法的code值
unsigned int code;
unsigned int flags;
long priority;
long saved_priority;
kuid_t sender_euid;
};
from,from_parent,to_proc等信息是用来在driver层帮助确认通信的来源和去向的。
code 还是方法的对应的code值
binder_buffer 存放binder服务和方法的参数等信息,它的定义在下面
binder_thread,binder_proc后面会介绍,binder_buffer这个是主要用来存放关键的通信数据的,它的定义在下面
struct binder_buffer {
struct list_head entry; /* free and allocated entries by address */
struct rb_node rb_node; /* free entry by size or allocated entry */
/* by address */
unsigned free:1;
// 是否允许上层释放空间
unsigned allow_user_free:1;
// 异步/同步
unsigned async_transaction:1;
unsigned debug_id:29;
struct binder_transaction *transaction;
// 目标node,会对应上层的binder服务
struct binder_node *target_node;
// 主要是参数信息
size_t data_size;
size_t offsets_size;
uint8_t data[0];
};
allow_user_free 是否允许上层释放占有的空间。
async_transaction 代表是同步还是异步通信。
target_node 指向binder服务。
data_size,offsets_size,data[0] 参数相关信息
2.3 binder通信协议总结
1. client请求server 协议cmd+数据 封包拆包过程
client进程把BC_TRANSACTION + binder_transaction_data(binder服务引用 handle,方法code,方法参数) 传递到driver层
"driver层代理进程 clent"收到数据后把binder_transaction_data的数据进行拆包,把binder服务引用 handle,方法code,方法参数以及driver层的其他数据数据封包到binder_transaction中,把cmd为 BINDER_WORK_TRANSACTION和binder_transaction放入binder_work中。
“driver层代理进程 server”收到binder_work后(具体是怎么收到这个数据的后面的 “记录链路”结构体 会讲),把它拆包,把code,方法参数,binder服务(这时候handle已经转换为binder服务了) 放入 binder_transaction_data中。
“driver层代理进程 server” 把 BR_TRANSACTION + binder_transaction_data 传递给 server进程,server进程在它自己的biner线程中 把 code,方法参数,binder服务 解析出来后开始执行binder服务方法。
cmd转换过程 BC_TRANSACTION--->BINDER_WORK_TRANSACTION- -->BR_TRANSACTION
2. server返回结果给client 协议cmd+数据 封包拆包过程
server进程把 BC_REPLY + binder_transaction_data(返回结果) 传递到driver层
"driver层代理进程 server"收到数据后把binder_transaction_data的数据进行拆包,把返回结果和driver层的一些数据封包到binder_transaction中,把cmd为BINDER_WORK_TRANSACTION和binder_transaction封包到binder_work中。
“driver层代理进程 client”收到binder_work后(具体是怎么收到这个数据的后面的 “记录链路”结构体 会讲),把它拆包,把返回结果 放入 binder_transaction_data中。
“driver层代理进程 client” 把BR_REPLY + binder_transaction_data 传递给 client进程,client进程把返回结果解析出来
cmd转换过程 BC_REPLY--->BINDER_WORK_TRANSACTION--->BR_REPLY
03
—
"记录链路"结构体
为什么要叫作 "记录链路"结构体, 我个人理解driver层的binder_proc,binder_node等结构体主要有两个关键作用:
记录上层进程中的信息,从而在driver层能够有信息与上层进程的信息对 应起来。
“链路”是指这些结构体为binder进程通信搭建了一条链路,那通信协议等可以在链路上传输了。
3.1 binder_node
3.1.1 binder_node结构体
binder_node记录了 进程中binder服务(Binder,BBinder) 的关键信息 ,,这样就可以与Binder,BBinder形成了对应关系,它的定义如下:
struct binder_node {
int debug_id;
struct binder_work work;
union {
struct rb_node rb_node;
struct hlist_node dead_node;
};
// 所属进程相关信息
struct binder_proc *proc;
struct hlist_head refs;
int internal_strong_refs;
int local_weak_refs;
int local_strong_refs;
// 指向上层的binder服务
binder_uintptr_t ptr;
binder_uintptr_t cookie;
// 引用相关信息
unsigned has_strong_ref:1;
unsigned pending_strong_ref:1;
unsigned has_weak_ref:1;
unsigned pending_weak_ref:1;
unsigned has_async_transaction:1;
unsigned accept_fds:1;
unsigned min_priority:8;
struct list_head async_todo;
};
has_strong_ref,has_weak_ref等主要用来记录binder_node引用方面的信息的。
binder_node只在当前的“driver层代理进程”中存在,它封装的是Binder(java层),BBinder(native层)的信息
3.1.2 binder_node生成时机
先来看段代码:
static void binder_transaction(struct binder_proc *proc,
struct binder_thread *thread,
struct binder_transaction_data *tr, int reply)
{
省略代码...
for (; offp < off_end; offp++) {
struct flat_binder_object *fp;
省略代码...
// fp 存储着上层进程的binder实例,这个binder是Binder,BBinder,BinderProxy,BpBinder类型中的某一个实例
fp = (struct flat_binder_object *)(t->buffer->data + *offp);
off_min = *offp + sizeof(struct flat_binder_object);
switch (fp->type) {
// 若当前是BINDER_TYPE_BINDER或者BINDER_TYPE_WEAK_BINDER,则能说明这个binder实例是binder服务(Binder,BBinder)
case BINDER_TYPE_BINDER:
case BINDER_TYPE_WEAK_BINDER: {
struct binder_ref *ref;
struct binder_node *node = binder_get_node(proc, fp->binder);
// 若从当前的binder_proc中没有取到binder_node,则创建一个binder_node来封装上层进程的binder实例信息,并把它存放于binder_proc的nodes红黑树中
if (node == NULL) {
node = binder_new_node(proc, fp->binder, fp->cookie);
省略代码...
}
// 从target_proc(binder_proc)中获取binder_ref,没有则依据binder_node创建binder_ref,并且把创建的binder_ref放入target_proc的refs红黑树中
ref = binder_get_ref_for_node(target_proc, node);
省略代码...
// 因为binder_ref是传递给 target_proc的,因此它是一个引用,它传递到 上层进程后的binder实例是(BinderProxy,BpBinder)
if (fp->type == BINDER_TYPE_BINDER)
fp->type = BINDER_TYPE_HANDLE;
else
fp->type = BINDER_TYPE_WEAK_HANDLE;
// 把desc赋值给handle
fp->handle = ref->desc;
省略代码...
} break;
// 若当前是BINDER_TYPE_HANDLE或者BINDER_TYPE_WEAK_HANDLE,则能说明这个binder实例是binder服务引用(BinderProxy,BpBinder)
case BINDER_TYPE_HANDLE:
case BINDER_TYPE_WEAK_HANDLE: {
struct binder_ref *ref = binder_get_ref(proc, fp->handle);
省略代码...
// 若target_proc就是当前进程,则需要把binder实例是binder服务引用类型的转为 binder服务类型
if (ref->node->proc == target_proc) {
if (fp->type == BINDER_TYPE_HANDLE)
fp->type = BINDER_TYPE_BINDER;
else
fp->type = BINDER_TYPE_WEAK_BINDER;
fp->binder = ref->node->ptr;
fp->cookie = ref->node->cookie;
binder_inc_node(ref->node, fp->type == BINDER_TYPE_BINDER, 0, NULL);
省略代码...
} else {
struct binder_ref *new_ref;
// 从target_proc(binder_proc)中获取binder_ref,没有则依据binder_node创建binder_ref,并且把创建的binder_ref放入target_proc的refs红黑树中
new_ref = binder_get_ref_for_node(target_proc, ref->node);
if (new_ref == NULL) {
return_error = BR_FAILED_REPLY;
goto err_binder_get_ref_for_node_failed;
}
fp->handle = new_ref->desc;
省略代码...
}
} break;
省略代码...
}
}
binder_transaction这个方法后续还会介绍,上面是摘取了其中一段代码,这段代码有以下几个作用:
把当前proc(binder_proc)的数据拷贝到target_proc,这也是一直说的binder进程通信数据只拷贝一次的地方。
若proc中的数据是binder服务(Binder,BBinder),
2.1 则创建binder_node(封装了binder实例),把创建的binder_node存放在proc的nodes红黑树中。
2.2 在target_proc中创建binder_ref(指向刚刚创建的binder_node),创建成功binder_ref后会返回一个desc(这个值就是binder服务引用的handle),在把binder_ref放入target_proc的refs_by_desc红黑树中
2.3 把binder服务转换为binder服务引用(fp-type变为BINDER_TYPE_HANDLE),fp->handle = ref->desc,这样binder服务引用就会返回给target_proc对应的进程若proc中的数据是binder服务引用(BinderProxy,BpBinder)
3.1 若proc与target_proc相等,则把binder服务引用转为binder服务。
3.2 否则,重复2.2--2.3过程
从上面的流程可以分析得知,当binder通信过程中,传递的参数数据中包含binder服务时就会创建binder_node。
3.2 binder_ref
3.2.1 binder_ref结构体
binder_ref记录了 **进程中binder服务引用(BinderProxy, BpBinder)** 的关键信息(其实就是handle),从而与BinderProxy,BpBinder形成对应关系,看下它的定义
struct binder_ref {
/* Lookups needed: */
/* node + proc => ref (transaction) */
/* desc + proc => ref (transaction, inc/dec ref) */
/* node => refs + procs (proc exit) */
int debug_id;
struct rb_node rb_node_desc;
struct rb_node rb_node_node;
struct hlist_node node_entry;
// ref所属的进程
struct binder_proc *proc;
// 所指向的binder_node
struct binder_node *node;
// desc就是上层的handle
uint32_t desc;
int strong;
int weak;
struct binder_ref_death *death;
};
binder_ref是指向binder_node的引用,它存在于 使用binder服务的 “driver层代理进程”中。
desc就是BpBinder中的handle,因此在进行binder进程通信时,上层进程传递下来的BpBinder中的handle传递到driver层后,通过handle找到binder_ref,进而找到binder_node。关于查找过程后面会有讲解。
3.2.2 binder_ref创建时机
从上面 2.4.1.2 binder_node生成时机 可以得知,当两个binder_proc之间进行通信时通信参数数据包含binder服务或binder引用并且通信这两个binder_proc不相等,则会在目标binder_proc中创建binder_ref。binder_ref,及binder服务引用它们的创建都是一个被动的过程,binder服务引用是driver层创建成功后,把创建成功实例传给上层进程,因此BinderProxy它是被jni层代码初始化的。
3.3 binder_thread
3.3.1 binder_thread结构体
binder_thread与 进程中的binder线程(IPCThreadState) 是一一对应关系,binder线程是由ProcessState类孵化出来的,它的定义如下:
struct binder_thread {
// 所属进程信息
struct binder_proc *proc;
struct rb_node rb_node;
int pid;
int looper;
struct binder_transaction *transaction_stack;
struct list_head todo;
uint32_t return_error; /* Write failed, return error code in read buf */
uint32_t return_error2; /* Write failed, return error code in read */
/* buffer. Used when sending a reply to a dead process that */
/* we are also waiting on */
wait_queue_head_t wait;
struct binder_stats stats;
};
looper的值是
enum {
// driver层通知 进程启动binder线程,启动成功后进程返回这个值
BINDER_LOOPER_STATE_REGISTERED = 0x01,
// ProcessState启动binder主线程成功后发送给driver层这个值
BINDER_LOOPER_STATE_ENTERED = 0x02,
// 进程中的binder线程离开时,发送此值给driver层
BINDER_LOOPER_STATE_EXITED = 0x04,
BINDER_LOOPER_STATE_INVALID = 0x08,
// 内核线程处于等待状态,没有任何事务时 是这个值
BINDER_LOOPER_STATE_WAITING = 0x10,
// 需要返回结果的looper
BINDER_LOOPER_STATE_NEED_RETURN = 0x20
};
之一,它根据 收到上层binder线程的状态值 来改变自己的状态值。
transaction_stack 主要用于binder进程同步通信时,等待binder服务的结果时,它的值是发送给binder服务时的binder_transaction。
todo 这个有点意思,它其实就是一个队列,主要存放binder_work,就是一个生产者/消费者模式,其他“driver层代理进程”往todo里面放binder_work,这个内核线程消费binder_work。
3.3.2 binder_thread创建时机
上层进程ProcessState孵化一个binder线程后,binder线程通过IPCThreadState与driver层通信后,就会在driver层创建binder_thread。
3.4 binder_proc
3.4.1 binder_proc结构体
binder_proc与 进程(ProcessState) 是一一对应关系,ProcessState是单例的,先看下它的定义:
struct binder_proc {
struct hlist_node proc_node;
// binder_thread 红黑树
struct rb_root threads;
// binder_node 红黑树
struct rb_root nodes;
// binder_ref 红黑树
struct rb_root refs_by_desc;
struct rb_root refs_by_node;
// 上层进程id
int pid;
省略代码...
struct page **pages;
size_t buffer_size;
uint32_t buffer_free;
// 存放binder_work队列
struct list_head todo;
wait_queue_head_t wait;
struct binder_stats stats;
struct list_head delivered_death;
// 能开启的最大binder线程数,这个值是 ProcessState发送的
int max_threads;
// 正在请求 ProcessState开启binder线程的 请求数量
int requested_threads;
// 已经启动的binder线程数
int requested_threads_started;
// 当前空闲的处于等待事务的binder线程数
int ready_threads;
long default_priority;
struct dentry *debugfs_entry;
};
threads,nodes,refs_by_desc,refs_by_node分别是binder_thread,binder_node,binder_ref的红黑树,也就是binder_proc中存储了binder_thread,binder_node,binder_ref等信息,只不过为了效率是以红黑树的形式存储的。
todo同binder_thread中的队列一样,是用来存储binder_work的。
max_threads,requested_threads,requested_threads_started,ready_threads都是与binder线程有关的信息。
3.4.2 binder_proc创建时机
在open binder driver后就会创建binder_proc。
3.5 总结
3.5.1 "记录链路"结构体是怎么样链路的
binder_proc,binder_node,binder_ref这几个结构体一起在binder进程通信过程中起一个“链路”的作用。我们来简单描述下这个“链路”过程(下面的流程分析都是基于client进程已经拿到了binder服务引用BinderProxy,关于怎么拿到它后面还会介绍):
进程client最终通过 ioctl(fd,BINDER_WRITE_READ,binder_write_read)把数据传递到driver层
通过fd把对应的binder_proc找到
从binder_write_read中拿到handle值
根据handle从binder_proc的refs红黑树中取出binder_ref
根据binder_ref取到binder_node
根据binder_node取到target_proc(binder_proc)
把binder_node,通信数据等放入target_proc的todo队列中中或者target_proc的target_thread的todo队列中
唤醒target_proc中处于等待的内核线程,从binder_transaction中拿出binder服务传递给 上层进程,这样在binder线程中调用binder服务的相应方法。
这样只要这个 "链路"打通了,那在“链路”上就可以传递数据了(协议)
04
—
总结
最后我用一张图来总结本篇内容